package cn.org.rookie.jeesdp.mapper;

import cn.org.rookie.jeesdp.mapper.annotation.Column;
import cn.org.rookie.jeesdp.mapper.annotation.PrimaryKey;
import cn.org.rookie.jeesdp.mapper.annotation.Table;
import cn.org.rookie.jeesdp.mapper.annotation.Transient;
import cn.org.rookie.jeesdp.mapper.entity.ColumnInfo;
import cn.org.rookie.jeesdp.mapper.entity.TableInfo;
import cn.org.rookie.jeesdp.mapper.sql.Sql;
import cn.org.rookie.jeesdp.mapper.sql.Wrapper;
import cn.org.rookie.jeesdp.mapper.utils.ObjectUtils;
import cn.org.rookie.jeesdp.mapper.utils.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.builder.annotation.ProviderContext;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author LHT
 */

public class BaseMapperProvider {
    private static final ConcurrentHashMap<String, TableInfo> CONTEXT = new ConcurrentHashMap<>();

    private static final String WRAPPER = "wrapper";
    private static final String PARAM = "param";

    private static Class<?> getEntityType(ProviderContext context) {
        return (Class<?>) ((ParameterizedType) (context.getMapperType().getGenericInterfaces()[0])).getActualTypeArguments()[0];
    }

    private TableInfo getTableInfo(ProviderContext context) {
        Class<?> type = getEntityType(context);
        return CONTEXT.computeIfAbsent(type.getName(), k -> transform(type));
    }

    private TableInfo transform(Class<?> type) {

        TableInfo table = new TableInfo();
        Table tableAnno = type.getAnnotation(Table.class);
        if (tableAnno == null) {
            table.setName(StringUtils.humpToUnderline(type.getSimpleName()));
        } else {
            if (tableAnno.value().isEmpty()) {
                table.setName(StringUtils.humpToUnderline(type.getSimpleName()));
            } else {
                table.setName(tableAnno.value());
            }
        }

        List<String> fieldNames = new ArrayList<>();
        List<String> columnNames = new ArrayList<>();
        List<ColumnInfo> columns = new ArrayList<>();
        table.setColumns(columns);
        table.setFieldNames(fieldNames);
        table.setColumnNames(columnNames);
        List<Field> fields = ObjectUtils.getAllFields(type);
        for (Field field : fields) {
            Transient transientAnno = field.getAnnotation(Transient.class);
            if (transientAnno == null) {
                ColumnInfo column = new ColumnInfo();
                Column columnAnno = field.getAnnotation(Column.class);
                String fieldName = field.getName();
                fieldNames.add(fieldName);
                column.setFieldName(fieldName);
                if (columnAnno != null) {
                    String columnName = columnAnno.value();
                    column.setName(columnName.isEmpty() ? StringUtils.humpToUnderline(fieldName) : columnName);
                    column.setComment(columnAnno.comment());
                } else {
                    column.setName(StringUtils.humpToUnderline(fieldName));
                }
                PrimaryKey primaryAnno = field.getAnnotation(PrimaryKey.class);
                if (primaryAnno != null) {
                    table.setPrimaryKey(column);
                }
                columnNames.add(column.getName());
                columns.add(column);
            }
        }

        return table;
    }

    public String insert(ProviderContext context) {
        return Sql.of(getTableInfo(context)).insert().build();
    }

    public String delete(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return Sql.of(getTableInfo(context)).delete().byPrimaryKey(paramMap.get(PARAM)).build();
    }

    public String deleteBy(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return Sql.of(getTableInfo(context)).delete().where((Wrapper) paramMap.get(WRAPPER)).build();
    }

    public String update(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return Sql.of(getTableInfo(context)).update().byPrimaryKey(paramMap.get(PARAM)).build();
    }

    public String updateBy(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return Sql.of(getTableInfo(context)).update().where((Wrapper) paramMap.get(WRAPPER)).build();
    }

    public String find(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return Sql.of(getTableInfo(context)).select().byPrimaryKey(paramMap.get(PARAM)).build();
    }

    public String findBy(MapperMethod.ParamMap<?> paramMap, ProviderContext context) {
        return Sql.of(getTableInfo(context)).select().where((Wrapper) paramMap.get(WRAPPER)).build();
    }

    public String findAll(ProviderContext context) {
        return Sql.of(getTableInfo(context)).select().build();
    }

}
